Sužinokite apie saugią komunikaciją tarp skirtingų šaltinių naudojant PostMessage API. Išnagrinėkite jos galimybes, saugumo rizikas ir geriausias praktikas, kaip sumažinti pažeidžiamumus interneto programose.
Komunikacija tarp skirtingų šaltinių: Saugumo modeliai naudojant PostMessage API
Šiuolaikiniame internete programoms dažnai reikia sąveikauti su ištekliais iš skirtingų šaltinių. Tos pačios kilmės politika (angl. Same-Origin Policy, SOP) yra esminis saugumo mechanizmas, kuris riboja skriptų prieigą prie išteklių iš kitos kilmės vietos. Tačiau yra teisėtų scenarijų, kai komunikacija tarp skirtingų šaltinių yra būtina. postMessage API suteikia kontroliuojamą mechanizmą tam pasiekti, tačiau labai svarbu suprasti galimas saugumo rizikas ir įdiegti atitinkamus saugumo modelius.
Tos pačios kilmės politikos (SOP) supratimas
Tos pačios kilmės politika yra pagrindinė saugumo koncepcija interneto naršyklėse. Ji riboja tinklalapių galimybę teikti užklausas į kitą domeną, nei tas, iš kurio buvo pateiktas tinklalapis. Kilmę apibrėžia schema (protokolas), priegloba (domenas) ir prievadas (angl. port). Jei bent vienas iš šių elementų skiriasi, kilmės vietos laikomos skirtingomis. Pavyzdžiui:
https://example.comhttps://www.example.comhttp://example.comhttps://example.com:8080
Visa tai yra skirtingos kilmės vietos, ir SOP riboja tiesioginę skriptų prieigą tarp jų.
PostMessage API pristatymas
postMessage API suteikia saugų ir kontroliuojamą mechanizmą komunikacijai tarp skirtingų šaltinių. Ji leidžia skriptams siųsti pranešimus į kitus langus (pvz., iframe, naujus langus ar korteles), nepriklausomai nuo jų kilmės. Gaunantis langas gali klausytis šių pranešimų ir atitinkamai juos apdoroti.
Pagrindinė pranešimo siuntimo sintaksė yra:
otherWindow.postMessage(message, targetOrigin);
otherWindow: Nuoroda į tikslinį langą (pvz.,window.parent,iframe.contentWindowarba lango objektas, gautas išwindow.open).message: Duomenys, kuriuos norite siųsti. Tai gali būti bet koks JavaScript objektas, kurį galima serializuoti (pvz., eilutės, skaičiai, objektai, masyvai).targetOrigin: Nurodo kilmę, kuriai norite siųsti pranešimą. Tai yra esminis saugumo parametras.
Gaunančioje pusėje reikia klausytis message įvykio:
window.addEventListener('message', function(event) {
// ...
});
event objektas turi šias savybes:
event.data: Pranešimas, kurį atsiuntė kitas langas.event.origin: Lango, kuris atsiuntė pranešimą, kilmė.event.source: Nuoroda į langą, kuris atsiuntė pranešimą.
Saugumo rizikos ir pažeidžiamumai
Nors postMessage suteikia būdą apeiti SOP apribojimus, ji taip pat sukelia potencialių saugumo rizikų, jei nėra įgyvendinama atsargiai. Štai keletas dažniausių pažeidžiamumų:
1. Tikslinės kilmės neatitikimas
Nesugebėjimas patikrinti event.origin savybės yra kritinis pažeidžiamumas. Jei gavėjas aklai pasitiki pranešimu, bet kuri svetainė gali siųsti kenksmingus duomenis. Prieš apdorodami pranešimą, visada patikrinkite, ar event.origin atitinka numatytą kilmę.
Pavyzdys (pažeidžiamas kodas):
window.addEventListener('message', function(event) {
// NEDARYKITE TO!
processMessage(event.data);
});
Pavyzdys (saugus kodas):
window.addEventListener('message', function(event) {
if (event.origin !== 'https://trusted-origin.com') {
console.warn('Gautas pranešimas iš nepatikimos kilmės:', event.origin);
return;
}
processMessage(event.data);
});
2. Duomenų injekcija
Gautų duomenų (event.data) traktavimas kaip vykdomojo kodo arba tiesioginis jų įterpimas į DOM gali sukelti „Cross-Site Scripting“ (XSS) pažeidžiamumus. Prieš naudodami gautus duomenis, visada juos išvalykite (angl. sanitize) ir patikrinkite.
Pavyzdys (pažeidžiamas kodas):
window.addEventListener('message', function(event) {
if (event.origin === 'https://trusted-origin.com') {
document.body.innerHTML = event.data; // NEDARYKITE TO!
}
});
Pavyzdys (saugus kodas):
window.addEventListener('message', function(event) {
if (event.origin === 'https://trusted-origin.com') {
const sanitizedData = sanitize(event.data); // Įgyvendinkite tinkamą duomenų valymo funkciją
document.getElementById('message-container').textContent = sanitizedData;
}
});
function sanitize(data) {
// Čia įgyvendinkite patikimą duomenų valymo logiką.
// Pavyzdžiui, naudokite DOMPurify ar panašią biblioteką
return DOMPurify.sanitize(data);
}
3. „Man-in-the-Middle“ (MITM) atakos
Jei bendravimas vyksta per nesaugų kanalą (HTTP), MITM atakų vykdytojas gali perimti ir pakeisti pranešimus. Saugiam bendravimui visada naudokite HTTPS.
4. Užklausų klastojimas tarp svetainių (CSRF)
Jei gavėjas atlieka veiksmus remdamasis gautu pranešimu be tinkamo patikrinimo, puolėjas gali suklastoti pranešimus, siekdamas priversti gavėją atlikti nenumatytus veiksmus. Įdiekite CSRF apsaugos mechanizmus, pavyzdžiui, įtraukdami slaptą raktą (angl. token) į pranešimą ir patikrindami jį gavėjo pusėje.
5. Pakaitos simbolių naudojimas targetOrigin
Nustačius targetOrigin reikšmę *, bet kokia kilmės vieta gali gauti pranešimą. To reikėtų vengti, nebent tai yra absoliučiai būtina, nes tai paneigia kilme pagrįsto saugumo prasmę. Jei vis dėlto turite naudoti *, įsitikinkite, kad įdiegėte kitas stiprias saugumo priemones, pavyzdžiui, pranešimų autentifikavimo kodus (MAC).
Pavyzdys (venkite to):
otherWindow.postMessage(message, '*'); // Venkite naudoti '*' nebent tai absoliučiai būtina
Saugumo modeliai ir geriausios praktikos
Norėdami sumažinti rizikas, susijusias su postMessage, laikykitės šių saugumo modelių ir geriausių praktikų:
1. Griežtas kilmės patikrinimas
Visada patikrinkite event.origin savybę gavėjo pusėje. Palyginkite ją su iš anksto nustatytu patikimų kilmės vietų sąrašu. Palyginimui naudokite griežtą lygybę (===).
2. Duomenų valymas ir tikrinimas
Išvalykite ir patikrinkite visus duomenis, gautus per postMessage, prieš juos naudodami. Naudokite tinkamas valymo technikas, priklausomai nuo to, kaip duomenys bus naudojami (pvz., HTML simbolių pakeitimas, URL kodavimas, įvesties tikrinimas). Naudokite bibliotekas, tokias kaip DOMPurify, HTML valymui.
3. Pranešimų autentifikavimo kodai (MAC)
Įtraukite pranešimo autentifikavimo kodą (MAC) į pranešimą, kad užtikrintumėte jo vientisumą ir autentiškumą. Siuntėjas apskaičiuoja MAC naudodamas bendrinamą slaptą raktą ir įtraukia jį į pranešimą. Gavėjas perskaičiuoja MAC naudodamas tą patį bendrinamą slaptą raktą ir palygina jį su gautu MAC. Jei jie sutampa, pranešimas laikomas autentišku ir nepakeistu.
Pavyzdys (naudojant HMAC-SHA256):
// Siuntėjas
async function sendMessage(message, targetOrigin, sharedSecret) {
const encoder = new TextEncoder();
const data = encoder.encode(JSON.stringify(message));
const key = await crypto.subtle.importKey(
"raw",
encoder.encode(sharedSecret),
{ name: "HMAC", hash: "SHA-256" },
false,
["sign"]
);
const signature = await crypto.subtle.sign("HMAC", key, data);
const signatureArray = Array.from(new Uint8Array(signature));
const signatureHex = signatureArray.map(b => b.toString(16).padStart(2, '0')).join('');
const securedMessage = {
data: message,
signature: signatureHex
};
otherWindow.postMessage(securedMessage, targetOrigin);
}
// Gavėjas
async function receiveMessage(event, sharedSecret) {
if (event.origin !== 'https://trusted-origin.com') {
console.warn('Gautas pranešimas iš nepatikimos kilmės:', event.origin);
return;
}
const securedMessage = event.data;
const message = securedMessage.data;
const receivedSignature = securedMessage.signature;
const encoder = new TextEncoder();
const data = encoder.encode(JSON.stringify(message));
const key = await crypto.subtle.importKey(
"raw",
encoder.encode(sharedSecret),
{ name: "HMAC", hash: "SHA-256" },
false,
["verify"]
);
const signature = await crypto.subtle.sign("HMAC", key, data);
const signatureArray = Array.from(new Uint8Array(signature));
const signatureHex = signatureArray.map(b => b.toString(16).padStart(2, '0')).join('');
if (signatureHex === receivedSignature) {
console.log('Pranešimas yra autentiškas!');
processMessage(message); // Tęsti pranešimo apdorojimą
} else {
console.error('Pranešimo parašo patikrinimas nepavyko!');
}
}
Svarbu: Bendrinamas slaptas raktas turi būti saugiai sugeneruotas ir saugomas. Venkite kietai koduoti rakto kode.
4. Vienkartinių kodų (Nonce) ir laiko žymių naudojimas
Norėdami išvengti pakartojimo atakų, į pranešimą įtraukite unikalų vienkartinį kodą (angl. nonce) ir laiko žymę. Gavėjas gali patikrinti, ar vienkartinis kodas nebuvo panaudotas anksčiau ir ar laiko žymė yra priimtiname laiko intervale. Tai sumažina riziką, kad puolėjas pakartotinai panaudos anksčiau perimtus pranešimus.
5. Mažiausių privilegijų principas
Suteikite tik minimalias būtinas privilegijas kitam langui. Pavyzdžiui, jei kitam langui reikia tik skaityti duomenis, neleiskite jam rašyti duomenų. Kurdami savo komunikacijos protokolą, vadovaukitės mažiausių privilegijų principu.
6. Turinio saugumo politika (CSP)
Naudokite turinio saugumo politiką (CSP), kad apribotumėte šaltinius, iš kurių galima įkelti skriptus, ir veiksmus, kuriuos skriptai gali atlikti. Tai gali padėti sušvelninti XSS pažeidžiamumų, galinčių atsirasti dėl netinkamo postMessage duomenų tvarkymo, poveikį.
7. Įvesties duomenų tikrinimas
Visada patikrinkite gautų duomenų struktūrą ir formatą. Apibrėžkite aiškų pranešimo formatą ir užtikrinkite, kad gauti duomenys atitiktų šį formatą. Tai padeda išvengti netikėto elgesio ir pažeidžiamumų.
8. Saugus duomenų serializavimas
Naudokite saugų duomenų serializavimo formatą, pvz., JSON, pranešimams serializuoti ir deserializuoti. Venkite formatų, leidžiančių vykdyti kodą, pvz., eval() ar Function().
9. Pranešimų dydžio ribojimas
Apribokite per postMessage siunčiamų pranešimų dydį. Dideli pranešimai gali sunaudoti per daug išteklių ir potencialiai sukelti paslaugos trikdymo (angl. denial-of-service) atakas.
10. Reguliarūs saugumo auditai
Reguliariai atlikite savo kodo saugumo auditus, kad nustatytumėte ir pašalintumėte galimus pažeidžiamumus. Ypatingą dėmesį skirkite postMessage įgyvendinimui ir užtikrinkite, kad laikomasi visų saugumo geriausių praktikų.
Pavyzdinis scenarijus: Saugi komunikacija tarp „Iframe“ ir jo pagrindinio puslapio
Apsvarstykite scenarijų, kai „iframe“, esantis https://iframe.example.com, turi bendrauti su savo pagrindiniu puslapiu, esančiu https://parent.example.com. „Iframe“ turi siųsti vartotojo duomenis į pagrindinį puslapį apdorojimui.
Iframe (https://iframe.example.com):
// Sugeneruokite bendrinamą slaptą raktą (pakeiskite saugiu rakto generavimo metodu)
const sharedSecret = 'YOUR_SECURE_SHARED_SECRET';
// Gaukite vartotojo duomenis
const userData = {
name: 'John Doe',
email: 'john.doe@example.com'
};
// Siųskite vartotojo duomenis į pagrindinį puslapį
async function sendUserData(userData) {
const encoder = new TextEncoder();
const data = encoder.encode(JSON.stringify(userData));
const key = await crypto.subtle.importKey(
"raw",
encoder.encode(sharedSecret),
{ name: "HMAC", hash: "SHA-256" },
false,
["sign"]
);
const signature = await crypto.subtle.sign("HMAC", key, data);
const signatureArray = Array.from(new Uint8Array(signature));
const signatureHex = signatureArray.map(b => b.toString(16).padStart(2, '0')).join('');
const securedMessage = {
data: userData,
signature: signatureHex
};
parent.postMessage(securedMessage, 'https://parent.example.com');
}
sendUserData(userData);
Pagrindinis puslapis (https://parent.example.com):
// Bendrinamas slaptas raktas (turi sutapti su iframe raktu)
const sharedSecret = 'YOUR_SECURE_SHARED_SECRET';
window.addEventListener('message', async function(event) {
if (event.origin !== 'https://iframe.example.com') {
console.warn('Gautas pranešimas iš nepatikimos kilmės:', event.origin);
return;
}
const securedMessage = event.data;
const userData = securedMessage.data;
const receivedSignature = securedMessage.signature;
const encoder = new TextEncoder();
const data = encoder.encode(JSON.stringify(userData));
const key = await crypto.subtle.importKey(
"raw",
encoder.encode(sharedSecret),
{ name: "HMAC", hash: "SHA-256" },
false,
["verify"]
);
const signature = await crypto.subtle.sign("HMAC", key, data);
const signatureArray = Array.from(new Uint8Array(signature));
const signatureHex = signatureArray.map(b => b.toString(16).padStart(2, '0')).join('');
if (signatureHex === receivedSignature) {
console.log('Pranešimas yra autentiškas!');
// Apdorokite vartotojo duomenis
console.log('Vartotojo duomenys:', userData);
} else {
console.error('Pranešimo parašo patikrinimas nepavyko!');
}
});
Svarbios pastabos:
- Pakeiskite
YOUR_SECURE_SHARED_SECRETsaugiai sugeneruotu bendrinamu slaptu raktu. - Bendrinamas slaptas raktas turi būti vienodas tiek „iframe“, tiek pagrindiniame puslapyje.
- Šiame pavyzdyje pranešimų autentifikavimui naudojamas HMAC-SHA256.
Išvada
postMessage API yra galingas įrankis, leidžiantis bendrauti tarp skirtingų šaltinių interneto programose. Tačiau labai svarbu suprasti galimas saugumo rizikas ir įdiegti tinkamus saugumo modelius, kad šios rizikos būtų sušvelnintos. Laikydamiesi šiame vadove aprašytų saugumo modelių ir geriausių praktikų, galite saugiai naudoti postMessage kurdami patikimas ir saugias interneto programas.
Nepamirškite visada teikti pirmenybę saugumui ir sekti naujausias interneto kūrimo saugumo geriausias praktikas. Reguliariai peržiūrėkite savo kodą ir saugumo konfigūracijas, kad užtikrintumėte, jog jūsų programos yra apsaugotos nuo galimų pažeidžiamumų.